#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/poll.h>
#include <linux/fcntl.h>



#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "imx6uirq"
#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0xFF

struct irq_keydesc{
    int gpio;
    int irqnum;
    unsigned char value;
    char name[10];

    irqreturn_t (*handler)(int, void *);
    struct tasklet_struct tasklet;
};
struct imx6uirq_dev {
    int major ;
    int minor;
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *node;
    struct timer_list timer;
    struct irq_keydesc irqkey[KEY_NUM];
    struct fasync_struct *fasync_queue;

    atomic_t keyvalue;
    atomic_t keyrelease;
};
static struct imx6uirq_dev imx6uirqdev;
static int imx6uirq_fasync(int fd,struct file *file,int mode)
{
    struct imx6uirq_dev *dev = file->private_data;
    return fasync_helper(fd,file,mode,&dev->fasync_queue);
}
static int imx6uirq_open(struct inode *inode ,struct file *file)
{
    file->private_data = &imx6uirqdev;
    return 0;
}
static int imx6uirq_release(struct inode *inode,struct file *file)
{
    imx6uirq_fasync(-1, file, 0);
    return 0;
}
/**
 * @brief Read function for the imx6uirq character device.
 *
 * This function reads the current key value from the device's keyvalue atomic variable,
 * considering the keyrelease state. If a key has been released, it clears the high bit
 * of the keyvalue and copies the result to the user buffer. If no key has been released,
 * or there's an error, it returns -ENAVAIL.
 *
 * @param file Pointer to the file structure representing the opened device.
 * @param buf Pointer to the buffer where the read data should be copied (user space).
 * @param cnt The number of bytes to read (must match sizeof(keyvalue)).
 * @param offft Pointer to the read offset within the file (unused, set to NULL).
 *
 * @return On success, the number of bytes read, which is the size of keyvalue.
 *         On failure, -ENAVAIL if no key has been released or other errors occur.
 */
static ssize_t imx6uirq_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;                   // 初始化返回值为0
    unsigned char keyvalue;         // 定义存储按键值的变量
    unsigned char keyrelease;       // 定义存储按键释放状态的变量
    struct imx6uirq_dev *dev = file->private_data; // 获取设备私有数据指针

    printk(KERN_INFO "正在执行读取操作...\n"); // 打印日志信息，表明进入读取函数

    // 原子性地读取按键值和按键释放状态
    keyvalue = atomic_read(&dev->keyvalue);
    keyrelease = atomic_read(&dev->keyrelease);

    // 检查是否有按键释放事件
    if (keyrelease) {
        // 如果按键值的最高位（第8位，值为0x80）被设置，表示之前有按键按下
        if (keyvalue & 0x80) {
            // 清除按键值的最高位，准备发送给用户空间
            keyvalue &= ~0x80;
            // 尝试将按键值复制到用户空间缓冲区
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
            // 如果复制成功，ret将为实际复制的字节数
        } else {
            // 如果按键值没有最高位设置，跳转到错误处理
            goto data_error;
        }
        // 重置按键释放标志
        atomic_set(&dev->keyrelease, 0);
    } else {
        // 如果没有按键释放事件，同样跳转到错误处理
        goto data_error;
    }

    // 返回成功读取的字节数
    return ret;

    data_error:
    // 错误处理，返回-ENAVAIL表示当前无可用数据
    return -ENAVAIL;
}

static struct file_operations imx6uirq_fops = {
        .owner = THIS_MODULE,
        .read = imx6uirq_read,
        .open = imx6uirq_open,
        .release = imx6uirq_release,
        .fasync  = imx6uirq_fasync,
};
static irqreturn_t key_handler(int irq,void *dev_id){
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
    tasklet_schedule(&dev->irqkey[0].tasklet);
    return IRQ_HANDLED;
}
static void tasklet_func(unsigned long data)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev*)data;
    printk("tasklet_func is runing \r\n");
    dev->timer.data = data;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
}
static void timer_func(unsigned long data)
{
    int value;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)data;
    printk("timer_fun is runing\r\n");
    value = gpio_get_value(dev->irqkey[0].gpio);
    if (value == 0){  //    按下
        atomic_set(&dev->keyvalue, dev->irqkey[0].value);
    }else if (value ==  1){
        atomic_set(&dev->keyvalue, (dev->irqkey[0].value) | 0x80);
        atomic_set(&dev->keyrelease,1);
    }
    if (atomic_read(&dev->keyrelease))
    {
        kill_fasync(&dev->fasync_queue,SIGIO,POLL_IN);
    }
}

static int keyio_init(struct imx6uirq_dev *dev)
{
    int ret = 0,i = 0;
    /*1、按键初始化*/
    dev->node = of_find_node_by_path("/key");
    if (dev->node == NULL) {
        ret = -ENAVAIL;
        goto fail_node;
    }
    for (i = 0; i < KEY_NUM; ++i) {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->node, "key-gpios", i);
    }
    for (i = 0; i < KEY_NUM; ++i) {
        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "key%d", i);
        gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);
        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);       //获取中断编号
    }
    /*按键中断初始化*/
    dev->irqkey[0].value = KEY0VALUE;
    dev->irqkey[0].handler = key_handler;
    for (i = 0; i < KEY_NUM; ++i) {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                          dev->irqkey[i].name, dev);
        if (ret < 0) {
            goto fail_request;
        }
        tasklet_init(&dev->irqkey[i].tasklet, tasklet_func, (unsigned long) dev);
    }
    init_timer(&dev->timer);
    dev->timer.function = timer_func;
    return 0;
    fail_request:
    for (i = 0; i < KEY_NUM; i++) {
        gpio_free(dev->irqkey[i].gpio);

    }
    fail_node:
    return ret;
}
static int __init imx6uirq_init(void)
{
    int ret = 0;
    printk("imx6uirq init is runing \r\n");
    ret = alloc_chrdev_region(&imx6uirqdev.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    imx6uirqdev.major = MAJOR(imx6uirqdev.devid);
    imx6uirqdev.minor = MINOR(imx6uirqdev.devid);
    if (ret < 0) {
        goto fail_devid;
    }
    printk("major is %d ,minor is %d \r\n", imx6uirqdev.major, imx6uirqdev.minor);
    imx6uirqdev.cdev.owner = THIS_MODULE;
    cdev_init(&imx6uirqdev.cdev, &imx6uirq_fops);
    ret = cdev_add(&imx6uirqdev.cdev, imx6uirqdev.devid, IMX6UIRQ_CNT);
    if (ret < 0) {
        goto fail_cdevadd;
    }
    imx6uirqdev.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirqdev.class)) {
        ret = PTR_ERR(imx6uirqdev.class);
        goto fail_class;
    }
    imx6uirqdev.device = device_create(imx6uirqdev.class, NULL, imx6uirqdev.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirqdev.device)) {
        ret = PTR_ERR(imx6uirqdev.device);
        goto fail_device;
    }
    ret = keyio_init(&imx6uirqdev);
    if (ret < 0) {
        goto fail_keyinit;
    }
    return 0;
    fail_keyinit:
    device_destroy(imx6uirqdev.class, imx6uirqdev.devid);
    fail_device:
    class_destroy(imx6uirqdev.class);
    fail_class:
    cdev_del(&imx6uirqdev.cdev);
    fail_cdevadd:
    unregister_chrdev_region(imx6uirqdev.devid, IMX6UIRQ_CNT);
    fail_devid:
    return ret;
}
static void __exit imx6uirq_exit(void)
{
    int ret  = 0,i = 0;
    printk("imx6uirq exit is runing \r\n");
    for (i = 0; i < KEY_NUM; ++i) {
        free_irq(imx6uirqdev.irqkey[i].irqnum, &imx6uirqdev);
    }
    for (i = 0; i < KEY_NUM; i++) {
        gpio_free(imx6uirqdev.irqkey[i].gpio);
    }
    device_destroy(imx6uirqdev.class, imx6uirqdev.devid);
    class_destroy(imx6uirqdev.class);
    cdev_del(&imx6uirqdev.cdev);
    unregister_chrdev_region(imx6uirqdev.devid, IMX6UIRQ_CNT);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_AUTHOR("WXY");
MODULE_LICENSE("GPL");
